/*
 * except.c
 *
 *  Created on: Jan 23, 2025
 *      Author: hudong
 */
#include "../include/except.h"
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

static void error(const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  vfprintf(stderr, format, ap);
  va_end(ap);
  fflush(stderr);
  return;
}

typedef struct __ExceptFrame ExceptFrame;
struct __ExceptFrame {
  int status;
  sigjmp_buf env;
  Exception exception;
  ExceptFrame *prev;
};

static ExceptFrame *EStack;

enum { ENTERED = 0, CATCHED, THROWED, FINALIZED };

static jmp_buf *env() {
  ExceptFrame *newFrame = (ExceptFrame *)malloc(sizeof(ExceptFrame));
  newFrame->prev = EStack;
  newFrame->status = ENTERED;
  EStack = newFrame;
  return &(EStack->env);
}

static void status(int status) { EStack->status = status; }

static bool try() { return EStack->status == ENTERED; }

static bool catch (int type) {
  bool b = EStack->exception.type == type;
  if (b) {
    EStack->status = CATCHED;
  }
  return b;
}

static void finally() { EStack->status = FINALIZED; }

static Exception *exception() { return &(EStack->exception); }

static void throw(int type, const char *reason) {
  if (EStack != NULL) {
    EStack->status = THROWED;
    Exception *e = &(EStack->exception);
    e->type = type;
    e->reason = (char *)reason;
    siglongjmp(EStack->env, THROWED);
  } else {
    error("Unkonwn error type:%4d-%s\n", type, reason);
    abort();
  }
}

static void end() {
  ExceptFrame *curFrame = EStack;
  if (curFrame != NULL) {
    if (curFrame->status == THROWED && curFrame->prev != NULL) {
      Exception e = curFrame->exception;
      EStack = curFrame->prev;
      free(curFrame);
      throw(e.type, e.reason);
    } else {
      EStack = curFrame->prev;
      free(curFrame);
    }
  }
}

static void checkAlloc(void *p, const char *msg) {
  if (p == NULL)
    Try.throw(OutOfMemory, msg);
}

#undef __
#define __(m) .m = (m)

struct __IExcept Try = {__(env),     __(status),    __(try),       __(catch),
                        __(finally), __(end),       __(exception), __(throw),
                        __(error),   __(checkAlloc)};
